package com.dlc.shop.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dlc.shop.allinpay.service.AllinpayService;
import com.dlc.shop.bean.dto.UserWithDrawDTO;
import com.dlc.shop.bean.dto.allinpay.PayByBackSmsDTO;
import com.dlc.shop.bean.enums.UserBalanceLogType;
import com.dlc.shop.bean.enums.WithdrawFrequency;
import com.dlc.shop.bean.event.allinpay.AllinpayBalanceUpdateEvent;
import com.dlc.shop.bean.model.UserWithdrawCash;
import com.dlc.shop.bean.vo.DateUserWithdrawCashVO;
import com.dlc.shop.bean.vo.DistributionConfigVO;
import com.dlc.shop.bean.vo.UserWithdrawCashVO;
import com.dlc.shop.bean.vo.UserWithdrawInfoVO;
import com.dlc.shop.common.allinpay.constant.*;
import com.dlc.shop.common.allinpay.member.constant.VisitSourceType;
import com.dlc.shop.common.allinpay.member.resp.BindCard;
import com.dlc.shop.common.allinpay.order.resp.PayByBackSmsResp;
import com.dlc.shop.common.allinpay.order.resp.WithdrawApplyResp;
import com.dlc.shop.common.config.Constant;
import com.dlc.shop.common.constants.SegmentIdKey;
import com.dlc.shop.common.exception.YamiShopBindException;
import com.dlc.shop.common.util.PageParam;
import com.dlc.shop.config.ShopConfig;
import com.dlc.shop.dao.UserWithdrawCashMapper;
import com.dlc.shop.manager.impl.AllinpayManager;
import com.dlc.shop.service.SegmentService;
import com.dlc.shop.service.SysConfigService;
import com.dlc.shop.service.UserWithdrawCashService;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author gaozijie
 * @date 2023-04-24
 */
@Service
@AllArgsConstructor
public class UserWithdrawCashServiceImpl extends ServiceImpl<UserWithdrawCashMapper, UserWithdrawCash> implements UserWithdrawCashService {

    private static final Logger logger = LoggerFactory.getLogger(UserWithdrawCashServiceImpl.class);

    private final ApplicationContext applicationContext;

    private final ShopConfig shopConfig;

    private final UserWithdrawCashMapper userWithdrawCashMapper;

    private final AllinpayManager allinpayManager;
    private final AllinpayService allinpayService;
    private final SegmentService segmentService;
    private final SysConfigService sysConfigService;

    @Override
    public WithdrawApplyResp withdrawApply(UserWithDrawDTO withDrawDTO) {
        // 提现参数校验
        this.checkWithdrawParam(withDrawDTO);
        // 检查并调用通联接口
        WithdrawApplyResp withdrawApplyResp = this.checkAndApply(withDrawDTO);
        // 保存用户提现记录
        UserWithdrawCash withdrawCash = new UserWithdrawCash();
        withdrawCash.setUserId(withDrawDTO.getUserId());
        withdrawCash.setPaySysType(PaySysType.ALLINPAY.value());
        withdrawCash.setAmount(withDrawDTO.getAmount());
        withdrawCash.setBankCardNo(withDrawDTO.getBankCardNo());
        withdrawCash.setStatus(AllinpayWithdrawCashStatusType.APPLY_SUCCESS.value());
        withdrawCash.setOrderNo(withdrawApplyResp.getBizOrderNo());
        withdrawCash.setBizOrderNo(withdrawApplyResp.getOrderNo());
        userWithdrawCashMapper.save(withdrawCash);
        // 数据返回
        return withdrawApplyResp;
    }

    @Override
    public PayByBackSmsResp payConfirm(UserWithDrawDTO userWithDrawDTO) {
        // 数据校验
        UserWithdrawCash userWithdrawCash = userWithdrawCashMapper.getByOrderNo(userWithDrawDTO.getBizOrderNo());
        if (Objects.isNull(userWithdrawCash)) {
            throw new YamiShopBindException("请先获取验证码");
        }
        if (!Objects.equals(userWithdrawCash.getUserId(), userWithDrawDTO.getUserId())) {
            throw new YamiShopBindException("当前提现订单号不属于该用户");
        }
        if (!Objects.equals(userWithdrawCash.getStatus(), AllinpayWithdrawCashStatusType.APPLY_SUCCESS.value())
                && !Objects.equals(userWithdrawCash.getStatus(), AllinpayWithdrawCashStatusType.WAIT_PAY.value())) {
            throw new YamiShopBindException("申请状态异常，请刷新重试");
        }
        // 调用通联接口进行余额提现确认支付
        String bizUserId = AllinpayConstant.USER + userWithdrawCash.getUserId();
        PayByBackSmsDTO payByBackSmsDTO = new PayByBackSmsDTO();
        payByBackSmsDTO.setBizOrderNo(userWithDrawDTO.getBizOrderNo());
        payByBackSmsDTO.setVerificationCode(userWithDrawDTO.getVerificationCode());
        PayByBackSmsResp payByBackSmsResp = allinpayService.payByBackSms(bizUserId, payByBackSmsDTO);
        String payStatus = payByBackSmsResp.getPayStatus();
        String payFailMessage = payByBackSmsResp.getPayFailMessage();
        if (Objects.equals(payStatus, AllinpayPayStatus.UNPAY.value())) {
            // 收银宝快捷短信验证码输入错误场景，允许商户继续调用确认支付
            userWithdrawCash.setStatus(AllinpayWithdrawCashStatusType.WAIT_PAY.value());
            userWithdrawCash.setPayFailMessage(payFailMessage);
            userWithdrawCashMapper.update(userWithdrawCash);
        } else {
            userWithdrawCash.setStatus(AllinpayWithdrawCashStatusType.WITHDRAWING.value());
            userWithdrawCashMapper.update(userWithdrawCash);
        }
        return payByBackSmsResp;
    }

    @Override
    public UserWithdrawCash getByBizOrderNo(String bizOrderNo) {
        return userWithdrawCashMapper.getByOrderNo(bizOrderNo);
    }

    @Override
    public void update(UserWithdrawCash userWithdrawCash) {
        userWithdrawCashMapper.update(userWithdrawCash);
    }

    @Override
    public BigDecimal getTotalWithdrawAmount(String userId) {
        return userWithdrawCashMapper.getTotalWithdrawAmount(userId);
    }

    @Override
    public IPage<UserWithdrawCashVO> pageUserWithdrawCashVOList(String userId, PageParam<UserWithdrawCash> pageParam) {
        List<Integer> statuses = Arrays.asList(
                AllinpayWithdrawCashStatusType.WAIT_PAY.value(),
                AllinpayWithdrawCashStatusType.APPLY_SUCCESS.value()
        );
        PageParam<UserWithdrawCash> page = this.page(pageParam, new LambdaQueryWrapper<UserWithdrawCash>()
                .eq(UserWithdrawCash::getUserId, userId)
                .notIn(UserWithdrawCash::getStatus, statuses)
                .orderByDesc(UserWithdrawCash::getUpdateTime));
        IPage<UserWithdrawCashVO> withdrawCashPage = new Page<>();
        BeanUtils.copyProperties(page, withdrawCashPage);
        // 复制records
        if (CollectionUtils.isEmpty(page.getRecords())) {
            withdrawCashPage.setRecords(new ArrayList<>());
        } else {
            withdrawCashPage.setRecords(page.getRecords().stream().map(x -> {
                UserWithdrawCashVO withdrawCashVO = new UserWithdrawCashVO();
                BeanUtils.copyProperties(x, withdrawCashVO);
                return withdrawCashVO;
            }).collect(Collectors.toList()));
        }
        return withdrawCashPage;
    }

    @Override
    public IPage<DateUserWithdrawCashVO> pageDateUserWithdrawCashVOList(String userId, PageParam<UserWithdrawCash> pageParam) {
        // 分页查询用户提现记录
        IPage<UserWithdrawCashVO> withdrawCashPage = this.pageUserWithdrawCashVOList(userId, pageParam);
        IPage<DateUserWithdrawCashVO> dateWithdrawCashPage = new Page<>();
        BeanUtils.copyProperties(withdrawCashPage, dateWithdrawCashPage);
        if (CollectionUtils.isEmpty(withdrawCashPage.getRecords())) {
            dateWithdrawCashPage.setRecords(new ArrayList<>(Constant.INITIAL_CAPACITY));
            return dateWithdrawCashPage;
        }
        // 取出数据，对里面的数据按createTime的年月进行分组
        Map<Date, List<UserWithdrawCashVO>> dateUserWithdrawCashMap = new HashMap<>(16);
        DateTime beginOfMonth;
        List<UserWithdrawCashVO> userWithdrawCashVOList;
        for (UserWithdrawCashVO userWithdrawCashVO : withdrawCashPage.getRecords()) {
            beginOfMonth = DateUtil.beginOfMonth(Date.from(userWithdrawCashVO.getCreateTime().atZone(ZoneId.systemDefault()).toInstant()));
            userWithdrawCashVOList = dateUserWithdrawCashMap.getOrDefault(beginOfMonth, new ArrayList<>(Constant.INITIAL_CAPACITY));
            userWithdrawCashVOList.add(userWithdrawCashVO);
            dateUserWithdrawCashMap.put(beginOfMonth, userWithdrawCashVOList);
        }
        // 数据封装为DateUserWithdrawCashVO
        List<DateUserWithdrawCashVO> dateUserWithdrawCashVOList = new ArrayList<>(Constant.INITIAL_CAPACITY);
        DateUserWithdrawCashVO dateUserWithdrawCashVO;
        for (Map.Entry<Date, List<UserWithdrawCashVO>> entry : dateUserWithdrawCashMap.entrySet()) {
            dateUserWithdrawCashVO = new DateUserWithdrawCashVO();
            dateUserWithdrawCashVO.setDate(entry.getKey());
            dateUserWithdrawCashVO.setWithdrawCashVOs(entry.getValue());
            dateUserWithdrawCashVOList.add(dateUserWithdrawCashVO);
        }
        // 封装到新pageVO，并进行返回
        dateUserWithdrawCashVOList = dateUserWithdrawCashVOList.stream().sorted((o1, o2) -> o2.getDate().compareTo(o1.getDate())).toList();
        dateWithdrawCashPage.setRecords(dateUserWithdrawCashVOList);
        return dateWithdrawCashPage;
    }

    private void checkWithdrawParam(UserWithDrawDTO userWithDrawDTO) {
        // 获取提现配置(分销配置)
        DistributionConfigVO distributionConfig = sysConfigService.getSysConfigObject(Constant.DISTRIBUTION_CONFIG, DistributionConfigVO.class);
        // 校验提现金额是否超出限制
        Double amount = userWithDrawDTO.getAmount();
        if (amount.compareTo(distributionConfig.getAmountMax()) > 0) {
            throw new YamiShopBindException("提现金额大于最大提现金额");
        }
        if (amount.compareTo(distributionConfig.getAmountMin()) < 0) {
            throw new YamiShopBindException("提现金额小于最小提现金额");
        }
        // 校验提现时间频率
        Integer frequency = distributionConfig.getFrequency();
        if (!Objects.equals(frequency, WithdrawFrequency.NOT_LIMIT.getValue())) {
            LocalDateTime startTime = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
            WithdrawFrequency withdrawFrequency;
            if (Objects.equals(frequency, WithdrawFrequency.EVERY_DAY.getValue())) {
                // 每日
                withdrawFrequency = WithdrawFrequency.EVERY_DAY;
            } else if (Objects.equals(frequency, WithdrawFrequency.EVERY_WEEK.getValue())) {
                // 每周
                startTime = startTime.plusDays(-startTime.getDayOfWeek().getValue() + 1);
                withdrawFrequency = WithdrawFrequency.EVERY_WEEK;
            } else if (Objects.equals(frequency, WithdrawFrequency.EVERY_MONTH.getValue())) {
                // 每月
                startTime = startTime.plusDays(-startTime.getDayOfMonth() + 1);
                withdrawFrequency = WithdrawFrequency.EVERY_MONTH;
            } else {
                throw new YamiShopBindException("提现频率配置错误");
            }
            // 校验次数
            Integer times = userWithdrawCashMapper.countWithdrawTimes(userWithDrawDTO.getUserId(), startTime);
            if (times >= distributionConfig.getNumber()) {
                throw new YamiShopBindException("提现次数为" + withdrawFrequency.getDesc() + "只能提现" + distributionConfig.getNumber() + "次");
            }
        }
    }

    private WithdrawApplyResp checkAndApply(UserWithDrawDTO withDrawDTO) {
        String bizUserId = AllinpayConstant.USER + withDrawDTO.getUserId();
        List<BindCard> bindCards = allinpayService.queryBankCard(bizUserId, null);
        if (CollUtil.isEmpty(bindCards)) {
            throw new YamiShopBindException("银行卡不存在");
        }
        BindCard bindCard = bindCards.get(0);
        if (!Objects.equals(bindCard.getBindState(), 1)) {
            throw new YamiShopBindException("银行卡未绑定");
        }
        // 提现单号
        Long payId = segmentService.getDateFormatSegmentId(SegmentIdKey.PAY);
        String backUrl = shopConfig.getDomain().getApiDomainName() + AllinpayNoticeUrl.WITHDRAW_APPLY;
        // validateType:交易验证方式 0无验证 1短信验证码 2支付密码
        Long validateType = 1L;
        return allinpayManager.withdrawApply(payId, bizUserId, withDrawDTO.getAmount(), backUrl, bindCard, VisitSourceType.MOBILE.value(), validateType);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void withdrawBack(String bizOrderNo,
                             String payStatus,
                             String payFailMessage) {
        // 查询提现记录
        UserWithdrawCash withdrawCash = this.getByBizOrderNo(bizOrderNo);
        if (withdrawCash == null) {
            throw new YamiShopBindException("提现记录不存在");
        }
        // 更新提现记录
        boolean isOk = Objects.equals(payStatus, AllinpayPayStatus.OK.value());
        withdrawCash.setUpdateTime(LocalDateTime.now());
        if (isOk) {
            withdrawCash.setStatus(AllinpayWithdrawCashStatusType.CASH_SUCCESS.value());
            this.update(withdrawCash);
            logger.info("用户[{}]-订单[{}]余额提现成功", withdrawCash.getUserId(), bizOrderNo);
        } else {
            withdrawCash.setStatus(AllinpayWithdrawCashStatusType.CASH_FAIL.value());
            withdrawCash.setPayFailMessage(payFailMessage);
            this.update(withdrawCash);
            logger.error("用户[{}]-订单[{}]余额提现失败，原因：{}", withdrawCash.getUserId(), bizOrderNo, payFailMessage);
            return;
        }
        // 通联余额变动
        AllinpayBalanceUpdateEvent event = new AllinpayBalanceUpdateEvent();
        event.setUserId(withdrawCash.getUserId());
        event.setChangeBalance(-withdrawCash.getAmount());
        event.setType(UserBalanceLogType.WITHDRAW.value());
        applicationContext.publishEvent(event);
    }

    @Override
    public UserWithdrawInfoVO getUserWithdrawInfo(String userId) {
        UserWithdrawInfoVO userWithdrawInfoVO = userWithdrawCashMapper.getUserWithdrawInfo(userId);
        if (Objects.isNull(userWithdrawInfoVO)) {
            userWithdrawInfoVO = new UserWithdrawInfoVO();
            userWithdrawInfoVO.setApplyWithdrawAmount(0.0);
            userWithdrawInfoVO.setExtractedAmount(0.0);
        }
        userWithdrawInfoVO.setUnSettleAmount(0.0);
        return userWithdrawInfoVO;
    }
}
